<!DOCTYPE html>
<html lang="en">
    <head>
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta http-equiv="content-type" content="text/html; charset=utf-8">

      <!-- Enable responsiveness on mobile devices-->
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">

      <title>zmant</title>

      
        <link rel="alternate" type="application/rss+xml" title="RSS" href="https:&#x2f;&#x2f;itanq.github.io&#x2f;rss.xml">
      

      
          <link rel="stylesheet" href="https:&#x2f;&#x2f;itanq.github.io&#x2f;site.css">
      

      
      
    </head>

    <body>
        <div class="container">

            <div id="mobile-navbar" class="mobile-navbar">
              <div class="mobile-header-logo">
                <a href="/" class="logo">zmant</a>
              </div>
            </div>

            <header id="header">
                <div class="logo"><a href="https:&#x2f;&#x2f;itanq.github.io">zmant</a></div>
                <nav class="menu">
                    <ul>
                        
                            <li>
                                <a href="https://itanq.github.io">
                                    Home
                                </a>
                            </li>
                        
                            <li>
                                <a href="https://itanq.github.io&#x2f;categories">
                                    Categories
                                </a>
                            </li>
                        
                            <li>
                                <a href="https://itanq.github.io&#x2f;tags">
                                    Tags
                                </a>
                            </li>
                        
                            <li>
                                <a href="https://itanq.github.io&#x2f;about">
                                    About
                                </a>
                            </li>
                        
                    </ul>
                </nav>
            </header>

            <main>
                <div class="content" id="mobile-panel">
                    



<article class="post">
    
    <header class="post__header">
        <h1 class="post__title">
            <a href="https:&#x2f;&#x2f;itanq.github.io&#x2f;c-yuan-shi-zhi-zhen&#x2f;">c++原始指针</a>
        </h1>
        <div class="post__meta">
            <span class="post__time">2018-01-10</span>
            <span class="post__word_count">3045 Words</span>
            
        </div>
    </header>

    <div class="post-content">
      <h2 id="nei-cun-zhong-de-bian-liang">内存中的变量</h2>
<p>为理解指针，一定需要理解C/C++中“变量”的存储实质。考虑以下程序：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int main() {
    char ch=&#39;h&#39;;
    int i=10;
    double d=3.14;

    printf(&quot;ch:%c &amp;ch:%x sizeof(ch):%d \n&quot;, ch, &amp;ch, sizeof(ch));
    printf(&quot;i: %d &amp;i: %x sizeof(i): %d \n&quot;, i,  &amp;i,  sizeof(i) );
    printf(&quot;d: %d &amp;d: %x sizeof(d): %d \n&quot;, d,  &amp;d,  sizeof(d) );

    return 0;
}
</span></pre><p id="zola-continue-reading"><a name="continue-reading"></a></p>
<p>程序依次声明了一个字符类型变量，一个整形变量，一个双精度浮点型变量并赋给相应的值。那三个变量在内存中是如何表示的，不同的类型在内存中表示什么呢？
我们打印出上面的结果，可能如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">ch:h &amp;ch:0xbcf4179b sizeof(ch): 1
i: 10 &amp;i:0xbcf4179c sizeof(i) : 4
d: 3.14 &amp;d:0xbcf417a0 sizeof(d): 8
</span></pre>
<p>从结果可以看到字符型变量在内存中的大小是1个字节，内存起始地址是<code>0xbcf4179b</code>，整形变量在内存中的大小是4个字节，所以它的地址范围就是<code>0xbcf4179b+1=0xbcf4179c</code>到<code>0xbcf4179b+4=0xbcf4179f</code>，双精度浮点型变量在内存的大小的是8个字节，所以它的地址范围就是<code>0xbcf4179c+4=0xbcf417a0</code>到<code>0xbcf417a0+8=0xbcf417a7</code>。**所以，不同的类型在内存就表示了需要分配的不同的大小来容纳相应的变量。**简单表示如下：</p>
<p><img src="/images/pointer1.png" alt="" /></p>
<h2 id="zhi-zhen-shi-shi-yao">指针是什么</h2>
<p>先来声明一个指向整形变量的指针。如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int* pi = &amp;i;
</span></pre>
<p>我们知道，如上的语句声明了<code>pi</code>是一个指针，并且把变量<code>i</code>的地址赋给了它，但是它和上面声明的ch，i，d等变量有什么本质上的不同么？没有，它也只是一个变量而已。也是需要在内存中分配一块一定大小的内存空间来存储数据。如下图所示：</p>
<p><img src="/images/pointer2.png" alt="" /></p>
<p>增加如下打印语句：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">printf(&quot;pi: %x &amp;pi: %x *pi: %d sizeof(pi): %d\n&quot;, pi, &amp;pi, *pi, sizeof(pi));
</span></pre>
<p>那么其打印结果应该如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">pi: 0xbcf4179c &amp;pi: 0xbcf417a7 *pi: 10 sizeof(pi): 4
</span></pre>
<p>从上可以看出，<strong>指针变量和普通变量最主要的区别就是指针变量保存的是内存中的地址，而普通变量保存的是相应类型的值。</strong></p>
<h2 id="zhi-zhen-he-shu-zu-ming">指针和数组名</h2>
<p>我们都知道，**数组名代表了数组的首地址。**所以，对于下面的代码都是相同的，并没有任何区别：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int main() {
    int arr[]={1,2,3,4,5,6,7};
    for(int i=0; i&lt;7; ++i)
        std::cout &lt;&lt; &quot; &quot; &lt;&lt; arr[i];
    std::cout &lt;&lt; std::endl;

    for(int i=0; i&lt;7; ++i)
        std::cout &lt;&lt;&quot; &quot; &lt;&lt; *(arr+i);
    std::cout &lt;&lt; std::endl;

    int* parr = arr;
    for(int i=0; i&lt;7; ++i)
        std::cout &lt;&lt;&quot; &quot; &lt;&lt; parr[i];
    std::cout &lt;&lt; std::endl;

    for(int i=0; i&lt;7; ++i)
        std::cout &lt;&lt;&quot; &quot; &lt;&lt; *(parr+i);
    std::cout &lt;&lt; std::endl;

    return 0;
}
</span></pre>
<p>但是，对于下面的代码就不一样了：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int* parr = arr;
for(int i=0; i&lt;7; ++i) {
    std::cout &lt;&lt;&quot; &quot; &lt;&lt; *parr;
    ++parr;
}
</span></pre>
<p>对于上面的遍历方式，若是直接使用数组名来做则无法通过编译。原因在于<strong>数组名是一个常量指针，其值不能被修改。</strong></p>
<h2 id="const-xiu-shi-zhi-zhen">const 修饰指针</h2>
<h3 id="constyu-pu-tong-bian-liang">const与普通变量</h3>
<p>我们知道，如果需要一个值不能被改变的普通变量，可以使用<code>const</code>将其修饰为常量。竟然其值不能被改变，所以我们不能写出如下的代码：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">const int i;
i = 120;
</span></pre>
<p>上面的程序会报两个错误：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">prog.cc:8:15: error: uninitialized const &#39;i&#39; [-fpermissive]
     const int i;
               ^
prog.cc:9:9: error: assignment of read-only variable &#39;i&#39;
     i = 12;
</span></pre>
<p>一个对const常量未初始化，一个是对常量重新赋值。所以，我们可以知道两个有关常量的结论：<strong>一是常量必须在声明时进行初始化，二是常量的值不能被修改。</strong></p>
<p>其实，对于<code>const</code>修饰符的位置有两种写法：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">const type i1 = 120;
type const i2 = 210;
</span></pre>
<p>上面的两种写法是完全相同，也就是说：**类型标志符type与常量修饰符const哪个放前哪个放后都是一样的。**所以对于下面的两个指针，你应该知道他们的语义都是一样的：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int i1 = 23;
int i2 = 45;
const int * pi1 = &amp;i1;
int const * pi2 = &amp;i1;
</span></pre><h3 id="constyu-zhi-zhen-bian-liang">const与指针变量</h3>
<p>const即可修饰变量，也可修饰指针，同样可以同时修饰两者。也就是下面三行代码：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">const int * pi1;
int * const pi2;
const int * const pi3;
</span></pre>
<p>有个技巧，<strong>对于指针变量的声明语句，我们可以简单的从右往左读来分析上面的语义。</strong></p>
<p>先来看第一行代码：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">const int * pi1;
</span></pre>
<p>如果我们从右往左读的话是这样的：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">pi1              -&gt;    pi1是一个变量
* pi1            -&gt;    pi1是一个指针变量
int * pi1        -&gt;    pi1是一个指向int型变量的指针变量
const int * pi1  -&gt;    pi1是一个指向const int 型变量的指针变量
</span></pre>
<p>所以，我们确定pi1是一个指向常量的指针变量。这句话是说指针本身是变量，是可以被改变的，但指针指向的值是不可以被改变的，**确切的说是不能通过指针来改变的。**例如有以下代码：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int main() {
    int i1 = 123;
    int i2 = 234;

    const int * pi1 = &amp;i1;
    pi1 = &amp;i2;  // pi1可以在改变它内存中的值。

    i2 = 23445;  // i2是变量，也可以改变它内存中的值。
    std::cout &lt;&lt;&quot;i: &quot; &lt;&lt; *pi1 &lt;&lt; std::endl;

    return 0;
}
</span></pre>
<p>但是如果把上面<code>i2=23445</code>改为<code>*pi1=23445;</code>则肯定编译不过。可以看到，<code>pi1</code>的值是可以改变的，即它可以重新指向另一个地址，但是不能通过<code>*pi1</code>来修改<code>i2</code>的值，即使<code>i2</code>本身是个变量。</p>
<p>我们还可以这样来看，首先，<code>const</code>修饰的是整个<code>*pi1</code>，所以<code>*pi1</code>是常量，不能更改，比如<code>*pi1=23445;</code>无法通过编译；其次，<code>pi1</code>前面并没有<code>const</code>修饰，所以<code>pi1</code>是指针变量，能被赋值重新指向另一个地址，比如通过<code>pi1=&amp;i2;</code>可以改变<code>pi1</code>指向另一个地址。</p>
<p>同样对于第二行代码：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int * const pi2;
</span></pre>
<p>通过从右往左读分析出<code>pi2</code>是一个指向变量的常量指针。也就是说指针本身是常量不可更改，但指向的值是变量，是可以通过指针来更改的。</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int main() {
    int i1 = 123;
    int i2 = 234;

    int* const pi2 = &amp;i1;
    // pi2 = &amp;i2;  这里无法改变指针指向的地址。

    *pi2 = 450;
    std::cout &lt;&lt; &quot; i: &quot; &lt;&lt; *pi2 &lt;&lt; std::endl;

    return 0;
}
</span></pre>
<p>现在的情况和上面的情况完全相反了，对比上面的分析，我们知道是因为：</p>
<ol>
<li><code>pi2</code>因为<code>const</code>的修饰成了常量，无法修改<code>pi2</code>的值。</li>
<li>整个<code>*pi2</code>前面没有<code>const</code>修饰所以为变量，可以修改<code>*pi2</code>的值。</li>
</ol>
<p>所以，最后总结一下就是：</p>
<blockquote>
<ol>
<li>如果<code>const</code>修饰符在<code>*</code>的前面，则指针指向的值是常量无法修改。</li>
<li>如果<code>const</code>修饰符在<code>*</code>的后面，则指针本身是常量无法修改。</li>
<li>如果<code>const</code>修饰符在<code>*</code>的前后面都存在，则两个都是常量无法修改。</li>
</ol>
</blockquote>
<p>上面总结的第三点则针对了第三行代码的情况，故此不做额外分析。</p>
<h3 id="constzhi-zhen-zhi-xiang-de-bian-liang-qing-kuang">const指针指向的变量情况</h3>
<p>从前面的讨论可以看，可以把一个int型变量的地址赋值给一个指向const int 型变量的指针变量（常量），也即如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int i1 = 123;
const int * pi1 = &amp;i1;
</span></pre>
<p>这样的代码是合法的，但是我们是否可以把一个const型变量的地址赋值给一个指向int型变量的指针变量（常量）呢？也就是下面的代码是否合法：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">const int i2 = 234;
int *pi2 = &amp;i2;
</span></pre>
<p>显然上面的代码是无法通过编译的。原因在于把一个常量的地址赋给一个指向变量的指针会导致矛盾。比如，上面的代码中，<code>i2</code>是一个常量保证无法更改，但是若把<code>i2</code>的赋值赋给<code>pi2</code>的话，则<code>pi2</code>又保证可以通过<code>*pi2</code>修改它所指向的值，如此产生了矛盾。</p>
<p>而对于第一种情况则没有这种情况。</p>
<h2 id="zhi-xiang-zhi-zhen-de-zhi-zhen">指向指针的指针</h2>
<p>在前面我们知道，指针变量和其他普通变量一样，也是在某个内存地址中的，所以指针本身也是存在一个地址的。故而我们也可能让某个指针来指向这个指针的内存地址。如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">int i = 123;
int * pi = &amp;i;
int ** ppi = &amp;pi;
</span></pre>
<p>声明语句<code>int ** ppi = &amp;pi</code>声明了一个指针变量<code>ppi</code>，这个<code>ppi</code>用来存储一个<code>int*</code>类型的指针变量的地址。</p>
<p>如果i的地址为<code>0x88b9bf20</code>，pi的地址为<code>0x88b9bf2c</code>，那么：</p>
<p>ppi的值则为pi的地址：0x88b9bf2c
*ppi的值则为pi的地址所保存的地址：0x88b9bf20
**ppi的值则为pi的地址所保存的地址所对应的值：123</p>
<p>它们在内存中布局可能如下：</p>
<p><img src="/images/pointer3.png" alt="" /></p>
<p>假如我们需要通过函数参数来返回一个地址的话，那指向指针的指针就是必须的了，比如我们需要在一个字符串中找到第一个符合的字符的位置的时候，可能的代码如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">void find(char arr[], char ch, char* pa) {
    for(int i=0; *(arr+i); ++i) {
        if ( *(arr+i) == ch ) {
            pa = arr + i;
            break;
        }
        else
            pa = nullptr;
    }
}

int main()
{
    char arr[]={&quot;hello,c++ pointer&quot;};
    char ch = &#39;e&#39;;
    char * p = nullptr;

    find(arr, ch, p);

    if ( p == nullptr )
        std::cout &lt;&lt;&quot;not found&quot; &lt;&lt; std::endl;
    else
        std::cout &lt;&lt; &quot;found&quot;&lt;&lt;std::endl;
    return 0;
}
</span></pre>
<p>上面的代码肯定是输出的：not found。但实际上字符串的第二个字符既是我们要找的字符，但为什么结果却是not found呢，原因还是值传递的问题，我们传进去的指针<code>p</code>复制给了形参变量<code>pa</code>，所以对形参变量的修改并不会改变<code>p</code>的值。所以这里我们需要把指针变量<code>p</code>的地址传递进去。所以对find函数修改入下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">void find(char arr[], char ch, char** pa) {
    for(int i=0; *(arr+i); ++i) {
        if ( *(arr+i) == ch ) {
            *pa = arr + i;
            break;
        }
        else
            *pa = nullptr;
    }
}
</span></pre>
<p>则函数调用如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">find(arr, ch, &amp;p);  // 传递指针的地址进去。
</span></pre>
<p>需要注意的是：<strong>地址传递其实就是地址值的传递，和值传递没什么不同。</strong></p>
<h2 id="han-shu-ming-he-zhi-zhen-de-guan-xi">函数名和指针的关系</h2>
<p>我们知道，函数名其实就是函数的地址。那这个函数地址是否也可以像变量那样用一个相应的指针来存储呢？是的，可以。</p>
<h3 id="han-shu-zhi-zhen-bian-liang-de-sheng-ming">函数指针变量的声明</h3>
<p>就像某一个数据变量的内存地址可以存储在其相应的指针变量中一样，函数的首地址也可以存储在某个函数指针变量里。这样，我们就可以通过这个函数指针变量来调用相应的函数了。</p>
<p>和申明指向某种数据类型的指针一样，申明函数指针时，也必须指定函数指针指向的函数类型，这就意味着申明应指定函数的返回类型和函数的形参列表。如有如下函数：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">double* fun(int a, int b);
</span></pre>
<p>则申明指向上述函数的函数指针如下：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">double* (*pfunc)(int a, int b);
</span></pre>
<p>仔细比较可知，两个申明的方式一样只有<code>fun</code>和<code>(*pfun)</code>的区别。我们这么看，因为<code>fun</code>是函数,则<code>(*pfun)</code>也是函数，若<code>(*pfun)</code>是函数，那么<code>pfun</code>就是函数指针了。</p>
<h3 id="han-shu-zhi-zhen-bian-liang-de-diao-yong">函数指针变量的调用</h3>
<p>把函数指针指向要调用的函数地址之后，即可像正常函数调用那样调用，也可像函数指针那样，因为<strong>函数名本身是一个函数指针常量。</strong></p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">pfun = fun;                 // pfun指向fun函数
pfun = &amp;fun;                // pfun同样指向fun函数
double ans2 = (*pfun)(a,b); // 使用函数指针调用fun函数
double ans3 = pfunc(a,b);   // 也可以调用
double ans4 = (*func)(a,b); // 也可以调用
double ans1 = fun(a,b);     // 前面的调用都等同于此调用
</span></pre><h3 id="han-shu-zhi-zhen-de-qi-ta-shi-xiang">函数指针的其他事项</h3>
<p><code>typedef</code>的作用就是创建一个类型的别名，<code>typedef</code>也可以简化相应的函数指针，如：</p>
<pre style="background-color:#2b303b;">
<span style="color:#c0c5ce;">typedef double* (*pfun)(int a, int b);
pfun fun1 = f1;
pfun fun2 = f2;
</span></pre>
<p>函数指针同样可以作为函数的参数传递，使得调用方可已根据传入的函数做不同的事情。</p>

    </div>

    
    

    <div class="post-footer">
        
            
                <div class="post-tags">
                    
                        <a href="https:&#x2f;&#x2f;itanq.github.io&#x2f;tags&#x2f;c&#x2f;">#c++</a>
                    
                        <a href="https:&#x2f;&#x2f;itanq.github.io&#x2f;tags&#x2f;zhi-zhen&#x2f;">#指针</a>
                    
                </div>
            
            
                <div class="post-nav">
                    
                        <a class="previous" href="https:&#x2f;&#x2f;itanq.github.io&#x2f;cong-si-xing-dai-ma-kan-you-zhi-yin-yong&#x2f;">‹ 从4行代码看右值引用</a>
                    
                    
                    
                    
                </div>
            

        

    </div>

    
    
</article>


                </div>
            </main>

            
            
        </div>

    </body>

</html>
